package eventbus

import (
	"fmt"
	"reflect"
	"sync"

	"go.uber.org/zap"
)

func init() {
	rawConsumers = make(map[reflect.Type][]*EventConsumer)
}

var rawConsumers map[reflect.Type][]*EventConsumer
var runtimeConsumers sync.Map

type EventConsumer struct {
	invoker interface{}    //订阅者
	method  reflect.Method //被调用的函数
	argNum  int
}

func Register(bean interface{}) {
	rType := reflect.TypeOf(bean)
	if rType.Kind() != reflect.Pointer {
		panic("need pointer to register event")
	}
	methodNames := findEventMethodNames(rType.Elem(), make([]string, 0))
	for _, methodName := range methodNames {
		method, exist := rType.MethodByName(methodName)
		if !exist {
			panic(fmt.Sprintf("can not find method named %s", methodName))
		}
		if method.Type.NumIn() != 2 && method.Type.NumIn() != 3 {
			//第一个参数是结构体本身的指针
			panic("event listener must only obtain one args")
		}
		var pType reflect.Type
		if method.Type.NumIn() > 1 {
			//一般的listen函数，形如func (t *Listener) listen(event)
			pType = method.Type.In(1)
			if pType.Kind() != reflect.Pointer && pType.Kind() != reflect.Interface {
				panic(fmt.Sprintf("need pointer or interface to event args, but is %s", pType.Kind()))
			}
		}
		if method.Type.NumIn() > 2 {
			//在父类中实现的listen函数，形如func (t *Listener) listen(childInterface, event)
			pType = method.Type.In(2)
			if pType.Kind() != reflect.Pointer && pType.Kind() != reflect.Interface {
				panic(fmt.Sprintf("need pointer or interface to event args, but is %s", pType.Kind()))
			}
		}
		if pType.Kind() == reflect.Pointer {
			pType = pType.Elem()
		}

		consumers := rawConsumers[pType]
		if consumers == nil {
			consumers = make([]*EventConsumer, 0)
		}
		var eventConsumer EventConsumer
		eventConsumer.invoker = bean
		eventConsumer.method = method
		eventConsumer.argNum = method.Type.NumIn()
		consumers = append(consumers, &eventConsumer)
		rawConsumers[pType] = consumers
	}
	if len(methodNames) > 0 {
		zap.L().Info(fmt.Sprintf("EventBus register [%-25.25s] with [%2d] methods %s", rType.Elem().Name(), len(methodNames), methodNames))
	}
}

func findEventMethodNames(rType reflect.Type, eventNames []string) []string {
	for i := 0; i < rType.NumField(); i++ {
		if rType.Field(i).Type.Kind() == reflect.Struct {
			eventNames = findEventMethodNames(rType.Field(i).Type, eventNames)
		} else if rType.Field(i).Type.Kind() == reflect.Func {
			rTag := rType.Field(i).Tag.Get("event")
			if rTag == "" {
				continue
			}
			eventNames = append(eventNames, rTag)
		}
	}
	return eventNames
}

// 抛出事件
func Post(event interface{}) {
	comsumers := getConsumers(event)
	for _, comsumer := range comsumers {
		consum(comsumer, event)
	}
}

// golang中在for循环中进行异常捕获时，当发生异常将中断for循环的后续执行，需要把可能出现异常的代码片段抽成函数
func consum(comsumer *EventConsumer, event interface{}) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("err", err)
			// zap.L().Error("事件处理函数调用失败")
		}
	}()
	var callArguments []reflect.Value
	if comsumer.argNum == 2 {
		callArguments = []reflect.Value{reflect.ValueOf(comsumer.invoker), reflect.ValueOf(event)}
	} else if comsumer.argNum == 3 {
		callArguments = []reflect.Value{reflect.ValueOf(comsumer.invoker), reflect.ValueOf(comsumer.invoker), reflect.ValueOf(event)}
	}
	comsumer.method.Func.Call(callArguments)
}

func getConsumers(event interface{}) []*EventConsumer {
	rType := reflect.TypeOf(event)
	comsumers, exist := runtimeConsumers.Load(rType.Elem())
	if !exist {
		runtimeComsumers := make([]*EventConsumer, 0)
		for k, v := range rawConsumers {
			if k.Kind() == reflect.Struct && k == rType.Elem() {
				runtimeComsumers = append(runtimeComsumers, v...)
			} else if k.Kind() == reflect.Interface && rType.Implements(k) {
				runtimeComsumers = append(runtimeComsumers, v...)
			} else {
				continue
			}
		}
		runtimeConsumers.Store(rType.Elem(), runtimeComsumers)
		comsumers = runtimeComsumers
	}
	return comsumers.([]*EventConsumer)
}

func PrintComsumers() {
	fmt.Println(rawConsumers)
}
